#include <vector>
#include <iostream>
#include <fstream>
#include <cmath>
#include <cstring>
#include <string.h>
#include <time.h>
#include "triple.h"
#include "link.h"
#include "linkManager.h"
#include <Eigen/Dense>
#include <Eigen/Geometry>
#include "StickFigure.h"
#include "IkTestFigure.h"
#include "Megaman.h"

using namespace Eigen;
using namespace std;

#ifdef _WIN32
#	include <windows.h>
#else
#	include <sys/time.h>
#endif

#ifdef OSX
#include <GLUT/glut.h>
#include <OpenGL/glu.h>
#else
#include <GL/glut.h>
#include <GL/glu.h>
#endif

#include <time.h>
#include <math.h>

using namespace std;

#define PI 3.14159265
//****************************************************
// Some Classes
//****************************************************
class Viewport {
public:
	int w, h; // width and height
};


struct polygon {
	vector<triple*> myVertices;
	Link* myLink;
};
//****************************************************
// Global Variables
//****************************************************
Viewport	viewport;
float trans_x;
float trans_y;
float rot_x;
float rot_y;
float scalar;
const float rot_step = 5;
bool mouseDown;
float x_coord;
float y_coord;
float z_coord;
GLuint characterDL;
vector<polygon> myPolygons;
clock_t init;
StickFigure s;
IkTestFigure ik;
Megaman m;
bool wireframe;
bool walk_test;
bool ik_test;
bool megaman_test;
//****************************************************
// reshape viewport if the window is resized
//****************************************************
void myReshape(int w, int h) {
	viewport.w = w;
	viewport.h = h;

	glViewport(0,0,viewport.w,viewport.h);// sets the rectangle that will be the window
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();				// loading the identity matrix for the screen

	glOrtho(-1, 1, -1, 1, -100, 100);	// resize type = stretch
}


//****************************************************
// sets the window up
//****************************************************
void initScene(){
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Clear to black, fully transparent
	glEnable(GL_DEPTH_TEST); //enable Z-buffer
	myReshape(viewport.w,viewport.h);
	x_coord = -0.5;
	y_coord = -0.5;
	z_coord = 0;
	trans_x = 0;
	trans_y = 0;
	rot_x = 0;
	rot_y = 0;
	scalar = 0.25;
	mouseDown = false;
	init = clock();
	wireframe = false;
	walk_test = false;
	ik_test = false;
	megaman_test = false;
}


//***************************************************
// function that does the actual drawing
//***************************************************
void myDisplay() {

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// clear the color buffer (sets everything to black)
	// clear depth buffer
	glMatrixMode(GL_MODELVIEW);					// indicate we are specifying camera transformations

	glLoadIdentity();							// make sure transformation is "zero'd"


	//-----------------------set up lighting conditions--------------------
	float pos[4] = {-5.5f, -5.0f, -5.0f, 0.0f};

	glLightfv(GL_LIGHT0, GL_POSITION, pos);

	float Ia[4] = {1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv( GL_LIGHT0, GL_AMBIENT, Ia );	

	float Id[4] = {1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv( GL_LIGHT0, GL_DIFFUSE, Id );	

	float Is[4] = {1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv( GL_LIGHT0, GL_SPECULAR, Is );

	float pos2[4] = {5.5f, 5.0f, 5.0f, 0.0f};

	glLightfv(GL_LIGHT1, GL_POSITION, pos2);
	
	float Ia2[4] = {1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv( GL_LIGHT1, GL_AMBIENT, Ia2 );	

	float Id2[4] = {1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv( GL_LIGHT1, GL_DIFFUSE, Id2 );	

	float Is2[4] = {1.0f, 1.0f, 1.0f, 1.0f };
	glLightfv( GL_LIGHT1, GL_SPECULAR, Is2 );

	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	if(wireframe)
		glDisable(GL_LIGHTING);
	else
		glEnable(GL_LIGHTING);
	
	float ka[4] = {0.1f, 0.1f, 0.1f, 1.0f };
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ka );

	if(ik_test){
		float kd[4] = {1.0f, 0.84f, 0.0f, 1.0f };
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, kd );
	}
	else{
		float kd[4] = {0.9f, 0.9f, 0.9f, 1.0f };
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, kd );
	}
	float ks[4] = {0.9f, 0.9f, 0.9f, 1.0f };
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, ks );

	float p = 50.0f;
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, p );

	glEnable(GL_NORMALIZE);

	//----------------------- code to draw objects --------------------------
	
	glTranslatef(trans_x, trans_y, 0.0f);
	glRotatef(rot_x, 1.0f, 0.0f, 0.0f);
	glRotatef(rot_y, 0.0f, 1.0f, 0.0f);
	glScalef(scalar, scalar, scalar);
	
	Vector3f vec(x_coord, y_coord, z_coord);

	if(walk_test){
		double diff = clock() - init;
		if((diff/(double)CLOCKS_PER_SEC)>0.3){
			init = clock();
		s.walk(scalar);
		}
	}
	
	if(ik_test){
		ik.move(vec);
	}

	if(megaman_test){
		m.move(vec);
	}
	if(wireframe){
		if(walk_test)
			s.display();
		else if(megaman_test)
			m.display();
		else 
			ik.display();
	}


	for (int i = 0; i < (int) myPolygons.size(); i++) {
		polygon myPoly = myPolygons[i];
		vector<triple*> myPolyInfo = myPoly.myVertices;
        if (myPolyInfo.size() == 0) 
            continue;
		if(wireframe){
			glBegin(GL_LINE_LOOP);
			glColor3f(0,1,0);
		}else{
			if(walk_test)
				glBegin(GL_TRIANGLES);
			else
				glBegin(GL_QUADS);

			triple* a = myPolyInfo[0];
			triple* b = myPolyInfo[1];
			triple* c = myPolyInfo[2];

			Vector3f u = b->myVertex - a->myVertex;
			Vector3f v = c->myVertex - a->myVertex;
			Vector3f normal = u.cross(v);
		
			normal.normalize();
			glNormal3f(normal(0), normal(1), normal(2));

		}
		for (int j=0; j < (int) myPolyInfo.size(); j++) {
			triple* myTriple = myPolyInfo.at(j);
		
			//glColor3f(myTriple->myColor(0), myTriple->myColor(1), myTriple->myColor(2));
			//if (!myTriple->myNormal.isZero(0.0001)) {
				//glNormal3f(myTriple->myNormal(0), myTriple->myNormal(1), myTriple->myNormal(2));
			//}

			glVertex3f(myTriple->myVertex(0), myTriple->myVertex(1), myTriple->myVertex(2));

		}
		glEnd();		
	}

	
	glutSwapBuffers();					// swap buffers (we earlier set double buffer)
}

//****************************************************
// called by glut when there are no messages to handle
//****************************************************
void myFrameMove() {
	//nothing here for now
#ifdef _WIN32
	Sleep(10);						//give ~10ms back to OS (so as not to waste the CPU)
#endif
	glutPostRedisplay(); // forces glut to call the display function (myDisplay())
}
//****************************************************
// called by glut when certain keys are pressed

//****************************************************
void keyboard(unsigned char c, int x, int y){
	if(c == ' ')
		exit(0);
	else if(c =='=' || c == '+')
		scalar *= 1.1;
	else if(c =='-' || c == '_')
		scalar /= 1.1;
	else if(c =='z')
		z_coord +=0.1;
	else if(c =='x')
		z_coord -=0.1;
	else if(c == 'w')
		wireframe = !wireframe;
	else if(c == 9){
		if(ik_test)
			ik.switchBoneControl();
		if(megaman_test)
			m.switchBoneControl();
	}
	else{}
}

//****************************************************
// called by glut when special keys are pressed

//****************************************************
void specialKeyboard(int key, int x, int y) {
	int mod = 0;
	switch(key) {
	case GLUT_KEY_UP :
		mod = glutGetModifiers();
		if(mod == GLUT_ACTIVE_SHIFT)
			trans_y += 0.1;
		else
			rot_x -= rot_step;
		break;
	case GLUT_KEY_DOWN :
		mod = glutGetModifiers();
		if(mod == GLUT_ACTIVE_SHIFT)
			trans_y -= 0.1;
		else
			rot_x += rot_step;
		break;
	case GLUT_KEY_LEFT :
		mod = glutGetModifiers();
		if(mod == GLUT_ACTIVE_SHIFT)
			trans_x -= 0.1;
		else
			rot_y -= rot_step;
		break;
	case GLUT_KEY_RIGHT :
		mod = glutGetModifiers();
		if(mod == GLUT_ACTIVE_SHIFT)
			trans_x += 0.1;
		else
			rot_y += rot_step;
		break;

	}
}

void mouse(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN){
		mouseDown = true;
		x_coord = 2.0*x/viewport.w - 1 ;
		y_coord = - 2.0*y/viewport.h + 1;
	
	}
	else
		mouseDown = false;
}

void mouseMotion(int x, int y)
{
	if (mouseDown)
	{
		x_coord = 2.0*x/viewport.w - 1 ;
		y_coord = - 2.0*y/viewport.h + 1;
		glutPostRedisplay();
	}
}




void parseText(char* name) {
	vector<Vector3f> myVertices = vector<Vector3f>();
	vector<Vector3f> myColors = vector<Vector3f>();
	vector<Vector3f> myNormals = vector<Vector3f>();

	char str[255];
	float x;
	float y;
	float z;

	fstream myFile;
	myFile.open(name);

	if(myFile.fail()){
		cout<<"failed open input file!"<<endl;
		exit(1);
	}

	while (!myFile.eof()) {
		myFile >> str;
		if (strcmp(str, "v") == 0) {
			myFile >> x;
			myFile >> y;
			myFile >> z;
			Vector3f tempvec(x, y, z);
			myVertices.push_back(tempvec);
			//	cout<<tempvec;
		}

		if (strcmp(str, "vt") == 0) {
			myFile >> x;
			myFile >> y;
			if(megaman_test){
				z = 0;
			}
			else{
				myFile >> z;
			}
			Vector3f tempvec(x, y, z);
			myColors.push_back(tempvec);
			//	cout<<tempvec;
		}

		if (strcmp(str, "vn") == 0) {
			myFile >> x;
			myFile >> y;
			myFile >> z;
			Vector3f tempvec(x, y, z);
			myNormals.push_back(tempvec);
			//	cout<<tempvec;
		}

		if (strcmp(str, "f") == 0) {
			//cout << "iteration" << endl;
			char temp[255];
			polygon myPoly;
			myFile.getline(temp, 256);

			char* temp1 = strtok(temp, " ");
			vector<char*> list = vector<char*>();
			list.push_back(temp1);

			while (temp1) {
				//cout << temp1 << endl;
				temp1 = strtok(NULL, " ");
				list.push_back(temp1);
			}

			//cout << (int) list.size() << endl;

			for (int i = 0; i < (int) list.size() - 1; i++) {
				char* temp2 = list.at(i);
				//cout << *temp2 << endl;
				char* p = strtok(temp2, "/");

				int a = 0;
				int b = 0;
				int c = 0;
				int ii = 1;

				while (p) {
					//cout << *p << endl;
					if (ii == 1) {
						a = atoi(p);
						//cout << a << endl;
					}
					if (ii == 2) {
						b = atoi(p);
						//cout << b << endl;
					}
					if (ii == 3) {
						c = atoi(p);
						//cout << c << endl;
					}
					//cout << p << " " << ii << " " << a << "/" << b << "/" << c << endl;
					p = strtok(NULL, "/");
					ii++;
				}
				triple* myTriple = new triple;
				if (a) {
					myTriple->myVertex = myVertices.at(a - 1);
				} else {
					myTriple->myVertex = Vector3f(0, 0, 0);
				}
				if (b) {
					myTriple->myColor = myColors.at(b - 1);
				} else {
					myTriple->myColor = Vector3f(0, 0, 0);
				}
				if (c) {
					myTriple->myNormal = myNormals.at(c - 1);
				} else {
					myTriple->myNormal = Vector3f(0, 0, 0);
				}

				myPoly.myVertices.push_back(myTriple);
			}
			myPolygons.push_back(myPoly);
		}
	}

}


void parseArgs(int argc, char* argv[]) {
	cout<<"starting parsing"<<endl;
	if (strcmp(argv[1], "-walk_test") == 0) {
		parseText((char*) "test.obj");
		for(int i=0; i< (int) myPolygons.size(); i++){
		vector<triple*> vertices = myPolygons[i].myVertices;
		for(int j=0; j< (int) vertices.size(); j++){
			s.associateLinks(vertices[j]);		
		}	
		walk_test = true;
	}
	}
	else if(strcmp(argv[1], "-ik_test") == 0) {
		parseText((char*) "Figure.obj");
		ik_test = true;
		for(int i=0; i< (int) myPolygons.size(); i++){
			vector<triple*> vertices = myPolygons[i].myVertices;
			for(int j=0; j< (int) vertices.size(); j++){
				vertices[j]->myVertex = vertices[j]->myVertex/400;
				ik.associateLinks(vertices[j]);		
			}	
		}
		scalar = 1;
	}
	else if(strcmp(argv[1], "-megaman_test") ==0){
		megaman_test = true;
		parseText((char*) "Zero.obj");
		for(int i=0; i< (int) myPolygons.size(); i++){
			vector<triple*> vertices = myPolygons[i].myVertices;
			for(int j=0; j< (int) vertices.size(); j++){
				vertices[j]->myVertex = vertices[j]->myVertex/48 - Vector3f(0, 0.5, -0.01);
				m.associateLinks(vertices[j]);	
			}	
		}
		scalar = 1;
	}
	else{
	}
	cout<<"finishing parsing"<<endl;
}

//****************************************************
// the usual stuff, nothing exciting here
//****************************************************
int main(int argc, char *argv[]) {

	//This initializes glut
	glutInit(&argc, argv);

	//This tells glut to use a double-buffered window with red, green, and blue channels
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); //enable Z-buffer

	// Initalize theviewport size
	viewport.w = 800;
	viewport.h = 800;

	//The size and position of the window
	glutInitWindowSize(viewport.w, viewport.h);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Final Project");

	initScene();							// quick function to set up scene

	parseArgs(argc, argv);

	glutKeyboardFunc(keyboard);             // function to run when certain key strokes are detected
	glutSpecialFunc(specialKeyboard);      // function to call when special key strokes are detected
	glutMouseFunc(mouse);
	glutMotionFunc(mouseMotion);
	glutDisplayFunc(myDisplay);				// function to run when its time to draw something
	glutReshapeFunc(myReshape);				// function to run when the window gets resized
	glutIdleFunc(myFrameMove);				// function to run when not handling any other task
	glutMainLoop();							// infinite loop that will keep drawing and resizing and whatever else
	
	return 0;
}

